/** @file gui_scrollbar.cpp
    @brief Scrollbar widget */
#include "gui/gui_scrollbar.h"

// User configurable variables
int G3::GUI::MinimumScrollingBarLength = 3;
int G3::GUI::MinimumScrollBarButtonSize = 3;

// Vertical by default
G3::GUI::SCROLLBAR::SCROLLBAR () {
    BgColor = G3::COL4F (0.5f, 0.5f, 0.5f, 1.0f);
    FgColor = G3::COL4F (1.0f, 1.0f, 1.0f, 1.0f);

    ScrollToUpmostLine = true;
    KeepUpmostLine = true;

    BarSocketHeight = 0.0f;

    TotalSize = 0;
    VisibleSize = 0;

    CurrentLine = 0;

    BarLength = 0.0f;
    BarPosition = 0.0f;

	WheelSensitivity = 10.0f;

    MouseChews = false;
    DraggingBar = false;

    doOnMouseMove (ScrollbarMouseMove);
    doOnMouseDown (ScrollbarMouseDown);
    doOnMouseUp (ScrollbarMouseUp);
}

// Sets the scrollbar vertical
void G3::GUI::SCROLLBAR::SetVertical () {
    if (!IsVertical ()) {
        int temp = ButtonDimensions.x;
        ButtonDimensions.x = ButtonDimensions.y;
        ButtonDimensions.y = temp;

        Rect.SwapAxis ();
    }
}

// Sets the scrollbar horizontal
void G3::GUI::SCROLLBAR::SetHorizontal () {
    if (IsVertical ()) {
        int temp = ButtonDimensions.x;
        ButtonDimensions.x = ButtonDimensions.y;
        ButtonDimensions.y = temp;

        Rect.SwapAxis ();
    }
}

// Sets scrollbar dimensions
void G3::GUI::SCROLLBAR::SetDimensions (int w, int h) {
    bool VerticalBefore = IsVertical ();

    if ((VerticalBefore && (w < 1 || h < MinimumScrollingBarLength)) ||
        (!VerticalBefore && (h < 1 || w < MinimumScrollingBarLength)))
        return;

    Rect.Right = Rect.Left + w;
    Rect.Bottom = Rect.Top + h;

    bool VerticalNow = IsVertical ();

    // Save the buttons' dimensions that remain constant. Only swap the axis..
    if (VerticalBefore != VerticalNow) {
        ButtonDimensions.x = ButtonDimensions.y;
    } else if (!VerticalBefore && VerticalNow) {
        ButtonDimensions.y = ButtonDimensions.x;
    }
    //Set the non-constant dimension equal to scrollbar local width
    if (VerticalNow) {
        ButtonDimensions.x = Rect.GetWidth ();
        if (Rect.GetHeight () < 3 * MinimumScrollBarButtonSize)
            Rect.Bottom = Rect.Top + 3 * MinimumScrollBarButtonSize;
    } else {
        ButtonDimensions.y = Rect.GetHeight ();
        if (Rect.GetWidth () < 3 * MinimumScrollBarButtonSize)
            Rect.Right = Rect.Left + 3 * MinimumScrollBarButtonSize;
    }

    // Recalculate the bar position
    CalculateBar ();
}

// Sets up/left down/right button dimensions
void G3::GUI::SCROLLBAR::SetButtonDimensions (int w, int h) {
    if (w < MinimumScrollBarButtonSize || h < MinimumScrollBarButtonSize)
        return;

    bool VerticalNow = IsVertical ();

    if (VerticalNow)
        ButtonDimensions.y = h;
    else
        ButtonDimensions.x = w;

    if (VerticalNow) {
        //Absolute maximum of bar length below MinimumScrollingBarLength pixels?
        if ((Rect.GetHeight () - 2 * ButtonDimensions.y) < MinimumScrollingBarLength) {
            ButtonDimensions.y = (Rect.GetHeight () - MinimumScrollingBarLength) / 2;
        }
    } else {
        //Absolute maximum of bar length below MinimumScrollingBarLength pixels?
        if ((Rect.GetWidth () - 2 * ButtonDimensions.x) < MinimumScrollingBarLength) {
            ButtonDimensions.x = (Rect.GetWidth () - MinimumScrollingBarLength) / 2;
        }
    }

    // Recalculate the scrolling bar length and position
    CalculateBar ();
}

// Calculates the bar length
void G3::GUI::SCROLLBAR::CalculateBar () {
    if (IsVertical ())
        BarSocketHeight = Rect.GetHeight () - 2 * ButtonDimensions.y;
    else
        BarSocketHeight = Rect.GetWidth () - 2 * ButtonDimensions.x;

    if (TotalSize == 0)
        BarLength = BarSocketHeight;
    else
        BarLength = (((float) VisibleSize) * BarSocketHeight) / ((float) TotalSize);

    if (BarLength < MinimumScrollingBarLength)
        BarLength = MinimumScrollingBarLength;

    SetViewPosition (CurrentLine);
}

// Updates the bar position by deltas
void G3::GUI::SCROLLBAR::UpdateBarPosition (int DeltaX, int DeltaY) {
    bool VerticalNow = IsVertical ();

    if (VerticalNow) {
        BarPosition += (float) DeltaY;
    	if (BarPosition < 0)
            BarPosition = 0.0f;
        if (BarPosition > (BarSocketHeight - BarLength))
            BarPosition = BarSocketHeight - BarLength;
    } else {
        BarPosition += (float) DeltaX;
        if (BarPosition < 0)
            BarPosition = 0.0f;
        if (BarPosition > (BarSocketHeight - BarLength))
            BarPosition = BarSocketHeight - BarLength;
    }
}

// An equation for calculating bar position
void G3::GUI::SCROLLBAR::SetViewPosition (int CurrentViewPosition) {
    int cvp = CurrentViewPosition;
    if (cvp < 0)
        cvp = 0;
    if (cvp > TotalSize)
        cvp = TotalSize;
    CurrentLine = cvp;

    if (!ScrollToUpmostLine) {
    	BarPosition = (((float) CurrentLine) * (BarSocketHeight - BarLength)) / ((float) TotalSize);
    } else {
    	BarPosition = (((float) CurrentLine) * (BarSocketHeight)) / ((float) TotalSize);
    	if (BarPosition + BarLength > BarSocketHeight)
    	   BarPosition = BarSocketHeight - BarLength;
    }

    SigChange (NULL, this);
}

// Just a reverse equation
void G3::GUI::SCROLLBAR::CalculateViewPosition () {
    if (!ScrollToUpmostLine)
        CurrentLine = (int) ceil ((BarPosition * ((float) TotalSize)) / (BarSocketHeight - BarLength));
    else
        CurrentLine = (int) ceil ((BarPosition * ((float) TotalSize)) / BarSocketHeight);

    if (CurrentLine < 0)
        CurrentLine = 0;
    if (CurrentLine > TotalSize)
        CurrentLine = TotalSize;

    if (KeepUpmostLine && CurrentLine > TotalSize - VisibleSize)
        CurrentLine = TotalSize - VisibleSize;

    SigChange (NULL, this);
}

// Gets the line number currently scrolled to
int G3::GUI::SCROLLBAR::GetViewPosition () {
    // This should never happen
    if (CurrentLine < 0)
        return 0;
    if (CurrentLine > TotalSize)
        return TotalSize;

    if (KeepUpmostLine && CurrentLine > TotalSize - VisibleSize)
        return TotalSize - VisibleSize;
    // If it does, there is a serious problem.. Ignore it..
    return CurrentLine;
}

// Sets total / visible window size and currently selected line parameters
void G3::GUI::SCROLLBAR::SetAttribs (int TotalWindowSize, int VisibleWindowSize, int CurrentViewPosition) {
    if (TotalWindowSize == TotalSize &&
        VisibleWindowSize == VisibleSize &&
        CurrentLine == CurrentViewPosition)
        return;

    if (TotalWindowSize < 1)
        TotalSize = 1;
    else
    	TotalSize = TotalWindowSize;

    if (VisibleWindowSize > TotalSize)
        VisibleSize = TotalSize;
    else {
        if (VisibleWindowSize < 1)
            VisibleSize = 1;
        else {
            if (VisibleWindowSize > TotalSize)
                VisibleSize = TotalSize;
            else
    	    	VisibleSize = VisibleWindowSize;
        }
    }

    if (CurrentViewPosition < 0)
        CurrentLine = 0;
    else {
        if (CurrentViewPosition > TotalSize)
            CurrentLine = TotalSize;
        else
    	    CurrentLine = CurrentViewPosition;
    }

    if (KeepUpmostLine && CurrentLine > TotalSize - VisibleSize)
        CurrentLine = TotalSize - VisibleSize;

    CalculateBar ();
}

// Get bar rect
G3::RECTI G3::GUI::SCROLLBAR::GetBarRect () {
    G3::RECTI r;
    float a = 0.0f;
    float b = 0.0f;
    float f = Rect.GetHeight ();
    float g = Rect.GetWidth ();

    if (IsVertical ()) {
        a = ButtonDimensions.y + BarPosition + 1.0f;
        f = BarLength - 1.0f;
    } else {
        b = ButtonDimensions.x + BarPosition + 1.0f;
        g = BarLength - 1.0f;
    }

    r.Left = Rect.Left + (unsigned int) b;
    r.Top = Rect.Top + (unsigned int) a;
    r.Right = r.Left + (unsigned int) g;
    r.Bottom = r.Top + (unsigned int) f;

    return r;
}

// Up / Left
G3::RECTI G3::GUI::SCROLLBAR::GetPrevButtonRect () {
    G3::RECTI r;

    r.Left = Rect.Left;
    r.Top = Rect.Top;
    r.Right = Rect.Left + ButtonDimensions.x;
    r.Bottom = Rect.Top + ButtonDimensions.y;

    return r;
}

// Down / Right
G3::RECTI G3::GUI::SCROLLBAR::GetNextButtonRect () {
    G3::RECTI r;

    r.Left = Rect.Right - ButtonDimensions.x;
    r.Top = Rect.Bottom - ButtonDimensions.y;
    r.Right = Rect.Right;
    r.Bottom = Rect.Bottom;

    return r;
}

// The socket, where the bar slides in
G3::RECTI G3::GUI::SCROLLBAR::GetBarSocketRect () {
    G3::RECTI r;

    r.Left = Rect.Left + ButtonDimensions.x;
    r.Top = Rect.Top + ButtonDimensions.y;
    r.Right = Rect.Right - ButtonDimensions.x;
    r.Bottom = Rect.Bottom - ButtonDimensions.y;

    return r;
}

// Mouse is chewing the bar?
void G3::GUI::SCROLLBAR::Clicked (int MouseX, int MouseY) {
    int x2 = 0, y2 = 0;
    bool vertical = false;

    if (DraggingBar) {
        // drag the bar
        SetMousePosition (MouseX, MouseY);
    }

    MouseChews = true;

    // Did we miss the whole scrollbar?
    if (MouseX < Rect.Left || MouseX > Rect.Right ||
        MouseY < Rect.Top || MouseY > Rect.Bottom) {
        return;
    }

    // Is the scrollbar vertical?
    vertical = IsVertical ();
    if (vertical) {
        x2 = Rect.Left;
        y2 = Rect.Bottom - ButtonDimensions.y;
    } else {
        x2 = Rect.Right - ButtonDimensions.x;
        y2 = Rect.Top;
    }
    // Clicked the Up / Left button?
    if (MouseX >= Rect.Left && MouseX <= Rect.Left + ButtonDimensions.x &&
        MouseY >= Rect.Top && MouseY <= Rect.Top + ButtonDimensions.x) {

        SetViewPosition (GetViewPosition () - 1);
        return;
    }
    // Clicked the Down / Right button?
    if (MouseX >= x2 && MouseX <= x2 + ButtonDimensions.x &&
        MouseY >= y2 && MouseY <= y2 + ButtonDimensions.y   ) {

        SetViewPosition (GetViewPosition () + 1);
        return;
    }

    // Get the bar rect
    G3::RECTI r = GetBarRect ();

    // Clicked on the bar?
    // Or clicked past the bar and mouse is in dragging mode?
    if ((MouseX >= r.Left && MouseX <= r.Right &&
         MouseY >= r.Top && MouseY <= r.Bottom) ||
        DraggingBar) {
        // Ignore just clicking
        if (!DraggingBar) {
            // So, we are not dragging yet?
            DraggingBar = true;
            LastMousePosition.x = MouseX;
            LastMousePosition.y = MouseY;
        }
        return;
    }

    // Mouse missed the bar and the buttons but clicked inside the scrollbar?
    if (vertical) {
    	if (MouseY < r.Top) {
            SetViewPosition (CurrentLine - VisibleSize);
    	}
        if (MouseY > r.Bottom) {
            SetViewPosition (CurrentLine + VisibleSize);
    	}
    } else {
        if (MouseX < r.Right) {
            SetViewPosition (CurrentLine - VisibleSize);
        }
        if (MouseX > r.Right) {
            SetViewPosition (CurrentLine + VisibleSize);
        }
    }
}

// mouse button not down anymore?
void G3::GUI::SCROLLBAR::UnClicked () {
    MouseChews = false;
    DraggingBar = false;
}

// Sets the mouse position on the bar
void G3::GUI::SCROLLBAR::SetMousePosition (int MouseX, int MouseY) {
    if (!MouseChews)
        return;

    G3::VEC2I Delta (MouseX - LastMousePosition.x, MouseY - LastMousePosition.y);
    LastMousePosition.x = MouseX;
    LastMousePosition.y = MouseY;

    if (DraggingBar) {
        UpdateBarPosition (Delta.x, Delta.y);
        CalculateViewPosition ();
    }
}

void G3::GUI::SCROLLBAR::SetWheelPosition (int MouseZ) {
	static int LastMouseZ = MouseZ;
	int DeltaZ = LastMouseZ - MouseZ;

	if (DeltaZ != 0) {
		UpdateBarPosition (0, (int) (DeltaZ * WheelSensitivity));
		CalculateViewPosition ();
		LastMouseZ = MouseZ;
	}
}

G3::GUI::SCROLLBAR::SIG_CONNECTION G3::GUI::SCROLLBAR::doOnMouseMove (NOTIFY_HANDLER aNotifyHandler) {
	Assert (aNotifyHandler, "G3::GUI::SCROLLBAR::doOnMouseMove: aNotifyHandler argument is a NULL pointer");

	SIG_CONNECTION c = SigMouseMove.connect (aNotifyHandler);
	ConnMouseMove.push_back (c);

	return c;
}

G3::GUI::SCROLLBAR::SIG_CONNECTION G3::GUI::SCROLLBAR::doOnChange (G3::GUI::SCROLLBAR::NOTIFY_HANDLER aNotifyHandler) {
    Assert (aNotifyHandler, "G3::GUI::SCROLLBAR::doOnChange aNotifyHandler argument is a NULL pointer");

	SIG_CONNECTION c = SigChange.connect (aNotifyHandler);
	ConnChange.push_back (c);

	return c;
}

void G3::GUI::ScrollbarMouseMove (G3::INPUT *Input, G3::GUI::WIDGET *Widget) {
    G3::GUI::SCROLLBAR *sc = (G3::GUI::SCROLLBAR *) Widget;

    Assert (sc, "G3::GUI::ScrollbarMouseMove: Invalid widget pointer..");

    sc->SetMousePosition (Input->Mouse.Position.x, Input->Mouse.Position.y);
}

void G3::GUI::ScrollbarMouseDown (G3::INPUT *Input, G3::GUI::WIDGET *Widget) {
    G3::GUI::SCROLLBAR *sc = (G3::GUI::SCROLLBAR *) Widget;

    Assert (sc, "G3::GUI::ScrollbarMouseMove: Invalid widget pointer..");

    sc->Clicked (Input->Mouse.Position.x, Input->Mouse.Position.y);
}

void G3::GUI::ScrollbarMouseUp (G3::INPUT *Input, G3::GUI::WIDGET *Widget) {
    G3::GUI::SCROLLBAR *sc = (G3::GUI::SCROLLBAR *) Widget;

    Assert (sc, "G3::GUI::ScrollbarMouseMove: Invalid widget pointer..");

    sc->UnClicked ();
}

void G3::GUI::SCROLLBAR::Draw (float aElapsedTime) {
    G3::RECTI rect;

    glPushMatrix ();
    glDisable (GL_TEXTURE_2D);

    rect = GetBarSocketRect ();

    glColor4f (BgColor.r, BgColor.g, BgColor.b, BgColor.a);

    glBegin (GL_QUADS);
        glVertex2i (rect.Left, -rect.Top);
        glVertex2i (rect.Right, -rect.Top);
        glVertex2i (rect.Right, -rect.Bottom);
        glVertex2i (rect.Left, -rect.Bottom);
    glEnd ();

    rect = GetBarRect ();

    glColor4f (FgColor.r, FgColor.g, FgColor.b, FgColor.a);

    glBegin (GL_LINE_LOOP);
        glVertex2i (rect.Left, -Rect.Top);
        glVertex2i (rect.Left, -Rect.Bottom);
        glVertex2i (rect.Right, -Rect.Bottom);
        glVertex2i (rect.Right, -Rect.Top);
    glEnd ();

    if (IsVertical ()) {
        rect = GetPrevButtonRect ();

        glBegin (GL_LINE_LOOP);
            glVertex2i (rect.Left, -Rect.Bottom);
            glVertex2i (rect.Left + (rect.Right - rect.Left) / 2, -Rect.Top);
            glVertex2i (rect.Right, -Rect.Bottom);
        glEnd ();

        rect = GetNextButtonRect ();

        glBegin (GL_LINE_LOOP);
            glVertex2i (rect.Left, -Rect.Top);
            glVertex2i (rect.Left + (rect.Right - rect.Left) / 2, -Rect.Bottom);
            glVertex2i (rect.Right, -Rect.Top);
        glEnd ();
    } else {
        rect = GetPrevButtonRect ();

        glBegin (GL_LINE_LOOP);
            glVertex2i (rect.Right, -Rect.Bottom);
            glVertex2i (rect.Left, -Rect.Top - (rect.Bottom - rect.Top) / 2);
            glVertex2i (rect.Right, -Rect.Top);
        glEnd ();

        rect = GetNextButtonRect ();

        glBegin (GL_LINE_LOOP);
            glVertex2i (rect.Left, -Rect.Bottom);
            glVertex2i (rect.Right, -Rect.Top - (rect.Bottom - rect.Top) / 2);
            glVertex2i (rect.Left, -Rect.Top);
        glEnd ();
    }

    glEnable (GL_TEXTURE_2D);
    glPopMatrix ();
}
